//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2005
// Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd. or one of its subsidiaries.
//
// Garmin IMG parser stuff.
//

# include "StdAfx.h"
# include "Globals.h"
# include "Log.h"
# include "GarminImg.h"

const char c_strImgSignature [7] = "DSKIMG";

inline
void _append_buf (char * _buf, size_t & _pos, const char * _str) {
	while (* _str && _pos < 128)
		_buf [_pos ++] = * (_str ++);
}

void Append6bitSymbol (int & _state, BYTE _bt, char _buf [128], size_t & _pos) {
	assert (_bt <= 0x3F);

	if (_pos >= 128)
		return;

    if (_state == 1) {
		//
		// Symbol after 0x1C.
		//
        if (_bt == 0)
			_buf [_pos ++] = '@';
		else if (_bt == 2)
            _buf [_pos ++] = '\'';
        else if (_bt >= 1 && _bt <= 0xF)     // !"#$%&'()*+,-./
            _buf [_pos ++] = 0x20 + _bt;
        else if (_bt >= 0x1A && _bt <= 0x1F) // :;<=>?
            _buf [_pos ++] = 0x20 + _bt;
        else if (_bt >= 0x2B && _bt <= 0x2F) // [\]^_
			_buf [_pos ++] = 0x30 + _bt;
        else
            ReportWarning ("Unexpected symbol in label: 0x1C 0x%02X.", _bt);
        _state = 0;
        return;
    } else if (_state == 2) {
		//
		// Symbol after 0x1B.
		//
        if (_bt >= 0 && _bt <= 0x1A) {            // `a-z, lowercase
			//_append_buf (_buf, _pos, "~[0x1b]");
			_buf [_pos ++] = 0x60 + _bt;
		} else if (_bt >= 0x20 && _bt <= 0x29) {  // 0-9
			_append_buf (_buf, _pos, "~[0x1b]");
			_buf [_pos ++] = 0x10 + _bt;
        } else if (_bt == 0x2B)
			// Separation: on the map visible only the second section (when over 1km), 
            // with the mouse sees displayed one the word completely, not separated.
			_append_buf (_buf, _pos, "~[0x1b2b]");
		else if (_bt == 0x2C)
			// Separation: on the map visible only the first section (when over 1km), 
            // with the mouse sees displayed one the word completely, not separated.
			_append_buf (_buf, _pos, "~[0x1b2c]");
		else
			ReportWarning ("Unexpected symbol in label: 0x1B, 0x%02X.", _bt);
        _state = 0;
        return;
    }
	assert (_state == 0);

	if (_bt >= 0x01 && _bt <= 0x1A)  // A-Z
		_buf [_pos ++] = 0x40 + _bt;
    else switch (_bt) {
        case 0:
			_buf [_pos ++] = ' ';
            return;
        case 0x1B: // Force lower case.
			_state = 2;
            return;
        case 0x1C: // The prefix code.
            _state = 1;
            return;
        case 0x1D:
			_append_buf (_buf, _pos, "~[0x1d]");
            return;
        case 0x1E:
			// Separation: on the map visible only the second section (when over 1 km), 
            // with the mouse sees displayed one the word completely, by blank separated.
			_append_buf (_buf, _pos, "~[0x1e]");
			return;
        case 0x1F:
			// Separation: on the map visible only the first section (when over 1 km), 
            // with the mouse sees displayed one the word completely, by blank separated.
            // Normaly used as a separator before elevation.
			_append_buf (_buf, _pos, "~[0x1f]");
			return;
        case 0x20: // 0-9
		case 0x21:
		case 0x22:
		case 0x23:
		case 0x24:
		case 0x25:
		case 0x26:
		case 0x27:
		case 0x28:
		case 0x29:
            _buf [_pos ++] = 0x10 + _bt;
			return;
        case 0x2A: // Interstate highway
			_append_buf (_buf, _pos, "~[0x2a]");
            return;
        case 0x2B: // Highway - shield
			_append_buf (_buf, _pos, "~[0x2b]");
            return;
        case 0x2C: // Highway - round symbol
			_append_buf (_buf, _pos, "~[0x2c]");
            return;
        case 0x2D: // Highway - small symbol - blue/red
			_append_buf (_buf, _pos, "~[0x2d]");
            return;
        case 0x2E: // Highway - big symbol
			_append_buf (_buf, _pos, "~[0x2e]");
            return;
        case 0x2F: // Highway - small symbol - white
			_append_buf (_buf, _pos, "~[0x2f]");
            return;
        default:
			assert (false);
            ReportWarning ("Unexpected symbol in label: 0x%02X.", _bt);
            return;
    }
}

/////////////////////////////////////////////////////////////////////////////

float ConvertB3ToDeg (const_B3_t _data) {
    long l = ConvertB3ToDWORD (_data);
    if (l >= 0x800000)
        l -= 0x1000000;
	return l*c_fGarminUnit;
}

long ConvertB2ToGU (size_t _cShiftBits, long _2b, long _3b0) {
	assert ((::labs (_2b) & 0xFFFF0000) == 0);
    if (_2b >= 0x8000)
        _2b -= 0x10000;

	assert ((::labs (_3b0) & 0xFF000000) == 0);
	if (_3b0 >= 0x800000)
		_3b0 -= 0x1000000;

	return _3b0 + (_2b << _cShiftBits);
}

void GetPointsFromBitstream (
	const BYTE * _pBitStream, size_t _cBytes, size_t _cShiftBits, BYTE _btBitDef, bool _bExtraBit,
	float _fMapMeanX,
	long _lX0, long _lY0, points_t & _points, bool * _pbPointIsNode
) {
	if (_cBytes == 0)
		return;

	//
	// Convert bit stream to byte stream to have faster access.
	//

	assert (_cBytes <= 65535);
	const size_t cBits = _cBytes*8; // max 524280 (0x7FFF8)
    BYTE * const pBits = reinterpret_cast<BYTE *> (alloca (cBits));
    for (size_t cByte = 0, cBit = 0; cByte < _cBytes; ++ cByte, cBit += 8) {
		for (size_t b = 0; b < 8; ++ b)
			pBits [cBit + b] = (_pBitStream [cByte] >> b) & 0x1;
    }

	// Bit index.
    size_t n = 0;   

	//
	// Determine number of bits per X and Y and the coding schema.
	//

    size_t cBitsPerX, cBitsPerY;
    int iSignY, iSignX; // May be 0, 1, -1.

	// Determine bits per X.
    if (pBits [n ++] == 0) {   // variable sign
        iSignX = 0;
        cBitsPerX = 1;
    } else {
		if (pBits [n ++] == 0) // always plus
	        iSignX = 1;
		else                   // always minus
			iSignX = -1;
		cBitsPerX = 0;
	}
	size_t bpx = _btBitDef & 0xF;
	if (bpx > 9)
		bpx = 2*bpx - 9;
	cBitsPerX += bpx + 2;

	// Determine bits per Y.
    if (pBits [n ++] == 0) {   // variable sign
        iSignY = 0;
        cBitsPerY = 1;
    } else {
		if (pBits [n ++] == 0) // always plus
			iSignY = 1;
		else                   // always minus
			iSignY = -1;
		cBitsPerY = 0;
    }
	size_t bpy = _btBitDef >> 4;
	if (bpy > 9)
		bpy = 2*bpy - 9;
	cBitsPerY += bpy + 2;

	const size_t cBitsPerPoint = cBitsPerX + cBitsPerY + (_bExtraBit ? 1 : 0);

	//
	// Prepare points output.
	//

	assert (cBits > n);
	_points.reserve (1 + (cBits - n)/(cBitsPerPoint));

	// The first point.
	point_t point (_lX0*c_fGarminUnit, _lY0*c_fGarminUnit);
	if (point.x > _fMapMeanX + 180.f)
		point.x -= 360.f;
	if (point.x < _fMapMeanX - 180.f)
		point.x += 360.f;
	_points.push_back (point);

	if (_pbPointIsNode)
		_pbPointIsNode [0] = false;
	size_t cPoint = 1;

	//
	// Parse the bit stream.
	//

	// Value for detecting special case of coding.
    const long lXSpecial = 1UL << (cBitsPerX - 1);
    const long lYSpecial = 1UL << (cBitsPerY - 1);

	long lX = _lX0, lY = _lY0;

	while (n + cBitsPerPoint <= cBits) {
		bool bPointIsNode = false;
		if (_bExtraBit) {
			bPointIsNode = pBits [n] != 0;
			++ n;
		}
		if (_pbPointIsNode && cPoint < 256)
			_pbPointIsNode [cPoint] = bPointIsNode;

		//
		// Parse delta x.
		//
		long lDX = 0;
		if (iSignX == 0) {
			// Variable.
			long d;
			while (true) {
				d = 0;
				for (size_t l = 0; l < cBitsPerX; ++ l) {
					assert (n < cBits);
					d |= (DWORD (pBits [n]) << l);
					++ n;
				}
				if (d != lXSpecial)     // special case
					break;
				lDX += d - 1;
			}
			if (d < lXSpecial)          // positive
				lDX += d;
			else {
				assert (d > lXSpecial); // negative
				lDX = (d - 2*lXSpecial) - lDX;
			}
		} else {
			// Fixed.
			for (size_t l = 0; l < cBitsPerX; ++ l) {
				assert (n < cBits);
				lDX |= (DWORD (pBits [n]) << l);
				++ n;
			}
			if (iSignX < 0)
				lDX = -lDX;
		}
		lDX <<= _cShiftBits;

		assert ((::labs (lDX) & 0xFF000000) == 0);
		if (lDX >= 0x800000)
			lDX -= 0x1000000;

		// 180 deg lon correction (for MS WorldMap).
		if (lDX < 0 && -lDX >= 0xF00000)
			lDX += 0x1000000;

		//
		// Parse delta y.
		//
		long lDY = 0;
		if (iSignY == 0) {
			// Variable.
			long d;
			while (true) {
				d = 0;
				for (size_t l = 0; l < cBitsPerY; ++ l) {
					assert (n < cBits);
					d |= (DWORD (pBits [n]) << l);
					++ n;
				}
				if (d != lYSpecial)     // special case
					break;
				lDY += d - 1;
			}
			if (d < lYSpecial)          // positive
				lDY += d;
			else {
				assert (d > lYSpecial); // negative
				lDY = (d - 2*lYSpecial) - lDY;
			}
		} else {
			// Fixed.
			for (size_t l = 0; l < cBitsPerY; ++ l) {
				assert (n < cBits);
				lDY |= (DWORD (pBits [n]) << l);
				++ n;
			}
			if (iSignY < 0)
				lDY = -lDY;
		}
		lDY <<= _cShiftBits;

		assert ((::labs (lDY) & 0xFF000000) == 0);
		if (lDY >= 0x800000)
			lDY -= 0x1000000;

		//
		// Save next point.
		//

		lX += lDX;
		lY += lDY;

		point.x = lX*c_fGarminUnit;
		point.y = lY*c_fGarminUnit;
		assert (-190 <= point.x && point.x <= 190);
		assert (-90  <= point.y && point.y <= 90);

		_points.push_back (point);
		++ cPoint;
	}

	// Make sure no whole byte rests.
	if (_bExtraBit)
		// NOTE: in some R&R maps, one final zero byte is possible.
		assert (n <= cBits && cBits <= n + 8);
	else
		assert (n <= cBits && cBits <= n + 7);

	return;
}
